package CGParser;

import java.awt.Color;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.vecmath.*;
import essential.Axis;
import essential.Scene;
import essential.Camera;
import primitives.*;
import shaders.*;
import CGParser.Parser.ParserException;

public class SCParser{
    private Parser p;

    public SCParser() {
    }

    public boolean parse(String filename, Scene scene) {
      
        System.out.println("Parsing "+filename);

        try {
            p = new Parser(filename);

            while (true) {
            	
                String token = p.getNextToken();
                    
                if (token == null)
                    break;
                if (token.equals("image")) {
                   System.out.println( "Reading image settings ...");
                    parseImageBlock(scene);
                }else if (token.equals("bucket")) {
                   System.out.println( "Reading bucket size ...");
                    parseBucket(scene);
                }else if (token.equals("trace-depths")) {
                   System.out.println( "Reading trace depths ...");
                    parseTraceBlock(scene);
                } else if (token.equals("camera")) {
                    parseCamera(scene);
                } else if (token.equals("shader")) {
                    if (!parseShader(scene))
                        return false;
                }else if (token.equals("object")) {
                    parseObjectBlock(scene);
                }else if (token.equals("instance")) {
                    parseInstanceBlock(scene);
                } else if (token.equals("light")) {
                    parseLightBlock(scene);
                }else
                    System.out.printf( "Unrecognized token %s", token);
            }
            p.close();
            
            
        } catch (ParserException e) {
            System.out.printf( "%s\n", e.getMessage());
            e.printStackTrace();
            return false;
        } catch (FileNotFoundException e) {
            System.out.printf( "%s\n", e.getMessage());
            return false;
        } catch (IOException e) {
            System.out.printf( "%s\n", e.getMessage());
            return false;
        }
        
       System.out.println( "Done parsing.");
        return true;
    }

    private void parseImageBlock(Scene scene) throws IOException, ParserException {
        p.checkNextToken("{");
        if (p.peekNextToken("resolution")) {
        	//"resolutionX"
        	scene.width = p.getNextInt();
        	// "resolutionY
        	scene.height = p.getNextInt();
        }
        
        if (p.peekNextToken("aa")) {	
            scene.aaMin = p.getNextInt();
        	scene.aaMax = p.getNextInt();
        }
        
        p.peekNextToken("samples");
        p.getNextToken();
        p.peekNextToken("filter");
        p.getNextToken();
      
        p.checkNextToken("}");
    }
    
    private void parseBucket(Scene scene) throws ParserException, IOException {
    	
    	int columSize = p.getNextInt();
    	
    	p.checkNextToken("column");
    	scene.threadsCount = (int)scene.width/columSize;
    	
    }


    private void parseTraceBlock(Scene scene) throws ParserException, IOException {
        p.checkNextToken("{");
        if (p.peekNextToken("diff"))
        	Shader.gdiff = (double)p.getNextInt()/10; // diff = SunFlowerDiff/10
        if (p.peekNextToken("refl"))
        	Shader.grefl = (double)p.getNextInt()/10;
        if (p.peekNextToken("refr"))
        	Shader.grefr = (double)p.getNextInt()/10;
        p.checkNextToken("}");
    }

    private void parseCamera(Scene scene) throws ParserException, IOException {
    	 Vector3d target;
         Vector3d origin;
         Vector3d up;
         double   fov;
         double   aspectRatio;
    	
    	p.checkNextToken("{");
        p.checkNextToken("type");

        String type = p.getNextToken();
        System.out.printf( "Reading %s camera ...\n", type);
        
        if (type.equals("pinhole")) {

            p.checkNextToken("eye");
            origin = parseVector();
            p.checkNextToken("target");
            target = parseVector();
            p.checkNextToken("up");
            up = parseVector();
            p.checkNextToken("fov");
            fov = p.getNextFloat();
            p.checkNextToken("aspect");
            aspectRatio =p.getNextFloat();
            
        }else {
            p.checkNextToken("}");
            return;
        }

        scene.camera = new Camera(target, origin, up, fov, aspectRatio);        
        p.checkNextToken("}");
        
   } 
    
    private boolean parseShader(Scene scene) throws ParserException, IOException {
      
    	p.checkNextToken("{");
        p.checkNextToken("name");
        String name = p.getNextToken();
        System.out.printf( "Reading shader: %s ...\n", name);
       
        p.checkNextToken("type");
        
        Shader shader = new MatteShader();
       
        
        if(p.peekNextToken("constant")){
        	
        }else if(p.peekNextToken("organic")){
  		   shader = new OrganicShader();
        }else if(p.peekNextToken("bump")){
    		   shader = new BumpShader(); 
    		   shader.color = shader.nextColor();
    		   
        } else if(p.peekNextToken("stone")){
   		   shader = new StoneShader();
        
        }else if(p.peekNextToken("clouds")){
  		   shader = new CloudsShader();
       
        }else if(p.peekNextToken("marble")){
      	   shader = new MarbleShader();
      	   
        }else if(p.peekNextToken("wood")){
 		   shader = new WoodShader();
      	  
        }else if(p.peekNextToken("water")){
 		  shader = new WaterShader();
      	  
      	  
        }else if(p.peekNextToken("fire")){
 		  shader = new FireShader();
      	  
      	  
        }else if(p.peekNextToken("glass")){
        		   shader.refr = 3;
        		   shader.refl = 0.001;
        		   shader.spec = 0.4;
        		   shader.diff = 0.6;
        }else if(p.peekNextToken("mirror")){
        		   shader.refl = 2;
        		   shader.refr = 0;
        		   shader.diff = 0;
        		   shader.spec = 1;
        		   
         }else if(p.peekNextToken("matte")){
        	 	  shader.diff = 0.3;
        	 	  shader.spec = 0.7;
        	 	  shader.refl = 0;
        	 	  shader.refr = 0;
         }else if(p.peekNextToken("diffuse")){
        	 
        		   shader.diff = 1;
        		   shader.spec = 0;
         }else if(p.peekNextToken("specular")){
        		   shader.spec = 1;
        		   shader.diff = 0;
         }else if(p.peekNextToken("random")){
        	 		shader.type = "random";
        	 
         }else{
        	   //Si no es ning�n otro type, se ignora
       		p.getNextToken();
         }
        
        if(p.peekNextToken("texture")) {
        	String texturePath = p.getNextToken();
     		System.out.printf("Reading shader texture %s\n",texturePath);

     		File  f = new File(texturePath);
        	shader.textureImage = ImageIO.read(f);
        	shader.color = Color.white;
                  
        	if(p.peekNextToken("color"))
            	shader.color = parseColor();
        	
        }else{ 
        	p.checkNextToken("color");
        	shader.color = parseColor();
        }
        
    	if(p.peekNextToken("color"))
    		shader.gradientColor = parseColor();
        

        scene.shadersMap.put(name, shader);
     	p.checkNextToken("}");

        
        return true;
    } 
    
    private void parseObjectBlock(Scene scene) throws ParserException, IOException {
        p.checkNextToken("{");

        String name = null;        
        Vector3d[] points = null;
        int[] triangles = null;
        Vector3d[] normals = null; 
        Vector2d[] uvs = null;
        Shader shader = null;
        Renderable object = null;
        Transform t = new Transform();
        String type = null;
        
        if(p.peekNextToken("shader")){
        	String shaderName = p.getNextToken();
    		shader = scene.shadersMap.get(shaderName);

    	    if(shader == null)
        		shader = new MatteShader();    
    		
        	if(shader.type!=null && shader.type.equals("random")){
        		shader = new MatteShader();
        		shader.color = shader.nextColor();
        	}
        	
        }
        
      	
        
        if(p.peekNextToken("type")){
            type = p.getNextToken();
        }
       
         if (p.peekNextToken("name"))
            name = p.getNextToken();
        else
            name = ""+(int)((1000*Math.random())%1000);
         
        if(p.peekNextToken("transform")){
             p.checkNextToken("{");
             while (!p.peekNextToken("}")) {

            	 if (p.peekNextToken("translate")) {
                     float x = p.getNextFloat();
                     float y = p.getNextFloat();
                     float z = p.getNextFloat();
                     t.translate(new Vector3d(x,y,z));
                 } else if (p.peekNextToken("scaleu")) {
                     float s = p.getNextFloat();
                     t.scale(new Vector3d(s,s,s));
                 } else if (p.peekNextToken("scale")) {
                     float x = p.getNextFloat();
                     float y = p.getNextFloat();
                     float z = p.getNextFloat();
                     t.scale(new Vector3d(x,y,z));
                 } else if (p.peekNextToken("rotatex")) {
                     float angle = p.getNextFloat();
                     t.rotate(Axis.X,angle);
                 } else if (p.peekNextToken("rotatey")) {
                     float angle = p.getNextFloat();
                     t.rotate(Axis.Y,angle);
                 } else if (p.peekNextToken("rotatez")) {
                     float angle = p.getNextFloat();
                     t.rotate(Axis.Z,angle);
                 } else if (p.peekNextToken("rotate")) {
                     float x = p.getNextFloat();
                     float y = p.getNextFloat();
                     float z = p.getNextFloat();
                     t.rotate(Axis.X,x);
                     t.rotate(Axis.Y,y);
                     t.rotate(Axis.Z,z);

                 	}
             }
        }
        
        if(p.peekNextToken("type")){
            type = p.getNextToken();
        }
       
        
         if (type.equals("generic-mesh")) {
             System.out.printf( "Reading generic mesh: %s ... \n", name);

              // parse vertices
              p.checkNextToken("points");
              int np = p.getNextInt();
              points = parseVector3dArray(np);
            
              // parse triangle indices
              p.checkNextToken("triangles");
              int nt = p.getNextInt();
              triangles = parseIntArray(nt*3);

              // parse normals
              p.checkNextToken("normals");
              if (p.peekNextToken("vertex"))
            	  normals = parseVector3dArray(np);
                 // scene.parameter("normals", "vector", "vertex", parseVectorArray(np));
           
              
              // WTF? ->   p.checkNextToken("none");
              // parse texture coordinates
              p.checkNextToken("uvs");
              if (p.peekNextToken("vertex"))
            	  uvs = parseVector2dArray(np);
              else{
            	  uvs = new Vector2d[triangles.length];
            	  for(int i = 0 ; i < triangles.length; i++){
            		  uvs[i] = new Vector2d();
            	  }
            	  
            	  p.getNextToken();
              }
              
              object = new Mesh(points,triangles,uvs,normals);
              
              
              
         }else if(type.equals("box")){
             System.out.printf( "Reading box: %s ... \n", name);
             object = new Box(new Vector3d(),1,1,1);
         }else if(type.equals("sphere")){
             System.out.printf( "Reading sphere: %s ... \n", name);
             Sphere sphere = new Sphere();
             
             if (p.peekNextToken("name")){
            	 name = p.getNextToken();
             }
             
             p.checkNextToken("c");
             Vector3d origin = parseVector();
             p.checkNextToken("r");
             double radius = p.getNextFloat();
             
             sphere.setCenter(origin);
             sphere.setRadius(radius);
        
             object = sphere;
             
             
         }else if(type.equals("plane")){
             System.out.printf( "Reading plane: %s ... \n", name);
             Plane plane = new Plane();
             
             if (p.peekNextToken("name")){
            	 name = p.getNextToken();
             }
              
             p.checkNextToken("p");
             Vector3d origin = parseVector();
             p.checkNextToken("n");
             Vector3d normal = parseVector();
             
             plane.setNormal(normal);
             plane.setD(origin.dot(normal));
             object = plane;
             
         }else{
             System.out.printf( "Renderable type not found: %s ... \n", name);
             p.checkNextToken("}");
             return;
         }
         
         
         // Set Renderable Object
         object.set(t);
         object.setShader(shader);
         
         
         object.freezeTransform();
         scene.geometriesMap.put(name,object);
         scene.renderables.add(object); 

         p.checkNextToken("}");

    }
     
    
    private void parseInstanceBlock(Scene scene) throws ParserException, IOException {
        
        Renderable object;
        Transform t = new Transform();
        String name = null;
        
    	p.checkNextToken("{");
        
    	if(p.peekNextToken("name"))
          name = p.getNextToken();

        System.out.printf("Reading instance: %s ...\n", name);

        p.checkNextToken("geometry");
        String geoname = p.getNextToken();
       
        object = scene.geometriesMap.get(geoname);
    
        if(object == null)
        {
        	p.checkNextToken("geometry not found");
        	return;
        }
        
        
        // Add renderable to the list of renderable objects
        scene.renderables.add(object);
        
        if(p.peekNextToken("transform")){
            p.checkNextToken("{");
            while (!p.peekNextToken("}")) {

           	 if (p.peekNextToken("translate")) {
                    float x = p.getNextFloat();
                    float y = p.getNextFloat();
                    float z = p.getNextFloat();
                    t.translate(new Vector3d(x,y,z));
                } else if (p.peekNextToken("scaleu")) {
                    float s = p.getNextFloat();
                    t.scale(new Vector3d(s,s,s));
                } else if (p.peekNextToken("scale")) {
                    float x = p.getNextFloat();
                    float y = p.getNextFloat();
                    float z = p.getNextFloat();
                    t.scale(new Vector3d(x,y,z));
                } else if (p.peekNextToken("rotatex")) {
                    float angle = p.getNextFloat();
                    t.rotate(Axis.X,angle);
                } else if (p.peekNextToken("rotatey")) {
                    float angle = p.getNextFloat();
                    t.rotate(Axis.Y,angle);
                } else if (p.peekNextToken("rotatez")) {
                    float angle = p.getNextFloat();
                    t.rotate(Axis.Z,angle);
                } else if (p.peekNextToken("rotate")) {
                    float x = p.getNextFloat();
                    float y = p.getNextFloat();
                    float z = p.getNextFloat();
                    t.rotate(Axis.X,x);
                    t.rotate(Axis.Y,y);
                    t.rotate(Axis.Z,z);

                	}
            }
       }
  
        if(p.peekNextToken("shader")){
            Shader shader;
			shader = scene.shadersMap.get(p.getNextToken());
			
        	// If shader is null it uses shader from geometry
			if(shader != null)
        		object.setShader(shader);
        	
        }
   
        object.apply(t);
        object.freezeTransform();

        p.checkNextToken("}");
    }
    
    
    

    private void parseLightBlock(Scene scene) throws ParserException, IOException {
        p.checkNextToken("{");
        p.checkNextToken("type");

        Sphere light = new Sphere();
        light.getShader().setLight(true);

        
        if (p.peekNextToken("point")) {
           System.out.println( "Reading point light ...");
           light.setRadius(0.001);
            Color pow;
            if (p.peekNextToken("color")) {
                pow = parseColor();
                light.getShader().setColor(pow);
                p.checkNextToken("power");
                light.getShader().setIntensity(p.getNextFloat()/100);
            } 
            
            p.checkNextToken("p");
            light.setCenter(parseVector());
         
        } else if (p.peekNextToken("spherical")) {
           System.out.println( "Reading spherical light ...");
            p.checkNextToken("color");
            Color pow = parseColor();
            light.getShader().setColor(pow);
            
            p.checkNextToken("radiance");
            light.getShader().setIntensity(p.getNextFloat()/100);
           
            p.checkNextToken("center");
            light.setCenter(parseVector());
            p.checkNextToken("radius");
            light.setRadius(p.getNextFloat());
            p.checkNextToken("samples"); p.getNextInt();
            
        }

        scene.lightRenderables.add(light);
        scene.renderables.add(light);
        light.freezeTransform();
        
        p.checkNextToken("}");
    }

    
    /* else if (p.peekNextToken("directional")) {
    System.out.println( "Reading directional light ...");
     p.checkNextToken("source");
     Point3 s = parsePoint();
     scene.parameter("source", s);
     p.checkNextToken("target");
     Point3 t = parsePoint();
     scene.parameter("dir", Point3.sub(t, s, new Vector3d()));
     p.checkNextToken("radius");
     scene.parameter("radius", p.getNextFloat());
     p.checkNextToken("emit");
     Color e = parseColor();
     if (p.peekNextToken("intensity")) {
         float i = p.getNextFloat();
         e.mul(i);
     } else
         System.out.println( "Deprecated color specification - please use emit and intensity instead");
     scene.parameter("radiance", e);
     scene.light(scene.getUniqueName("dirlight"), new DirectionalSpotlight());
 } else if (p.peekNextToken("ibl")) {
    System.out.println( "Reading image based light ...");
     p.checkNextToken("image");
     scene.parameter("texture", p.getNextToken());
     p.checkNextToken("center");
     scene.parameter("center", parseVector());
     p.checkNextToken("up");
     scene.parameter("up", parseVector());
     p.checkNextToken("lock");
     scene.parameter("fixed", p.getNextBoolean());
     int samples = numLightSamples;
     if (p.peekNextToken("samples"))
         samples = p.getNextInt();
     else
         System.out.println( "Samples keyword not found - defaulting to %d", samples);
     scene.parameter("samples", samples);
     ImageBasedLight ibl = new ImageBasedLight();
     ibl.init(scene.getUniqueName("ibl"), scene);
 } else if (p.peekNextToken("meshlight")) {
     p.checkNextToken("name");
     String name = p.getNextToken();
    System.out.println( "Reading meshlight: %s ...", name);
     p.checkNextToken("emit");
     Color e = parseColor();
     if (p.peekNextToken("radiance")) {
         float r = p.getNextFloat();
         e.mul(r);
     } else*/
    
    private Color parseColor() throws IOException, ParserException {
        if (p.peekNextToken("{")) {
            String space = p.getNextToken();
            Color c = null;
            if (space.equals("sRGB nonlinear")) {
                float r = p.getNextFloat();
                float g = p.getNextFloat();
                float b = p.getNextFloat();
                c = new Color(r, g, b);
            } else if (space.equals("sRGB linear")) {
                float r = p.getNextFloat();
                float g = p.getNextFloat();
                float b = p.getNextFloat();
                c = new Color(r, g, b);
            } else
                System.out.printf( "Unrecognized color space: %s", space);
            p.checkNextToken("}");
            return c;
        } else {
            float r = p.getNextFloat();
            float g = p.getNextFloat();
            float b = p.getNextFloat();
            return new Color(r, g, b);
        }
    }

    private Vector3d[] parseVector3dArray(int size) throws IOException,ParserException {
    	Vector3d[] v = new Vector3d[size];
    	for(int i = 0; i < size; i++){
    		v[i] = parseVector();
    	}
    	
    	return v;
    	
    }
    
    private Vector2d[] parseVector2dArray(int size) throws IOException {
    	Vector2d[] v = new Vector2d[size];
    	for(int i = 0; i < size; i++){
    		v[i] = parseVector2d();
    	}
    	
    	return v;
    	
    }
    private Vector2d parseVector2d() throws IOException  {
        float x = p.getNextFloat();
        float y = p.getNextFloat();
        return new Vector2d(x, y);
    }
    
    private Vector3d parseVector() throws IOException {
        float x = p.getNextFloat();
        float y = p.getNextFloat();
        float z = p.getNextFloat();
        return new Vector3d(x, y, z);
    }

    private int[] parseIntArray(int size) throws IOException {
        int[] data = new int[size];
        for (int i = 0; i < size; i++)
            data[i] = p.getNextInt();
        return data;
    }

  
}